package command;

import gui.GUICore;
import gui.GUIConstants.ViewState;
import card.BuildingCard;
import card.Card;
import card.CreatureCard;


import core.Battle;
import core.Constants;
import core.Core;
import core.MapUtil;
import core.Player;
import core.Rules;
import core.Tile;
import core.Constants.ConnectionType;
import core.Rules.Area;
import core.Rules.Cost;
import effect.Effect;
import effect.Effect.TargetType;

/**
 * 
 * @author Oscar
 *
 *A class with the purpose of checking if commands are valid or not.
 */

public class CommandValidator {
	private static CommandValidator me;
	private static Core core;
	
	public static CommandValidator getInstance(){
		if(me == null)
			me = new CommandValidator();
		return me;
	}
	private CommandValidator(){
		core=Core.getInstance();
	}
	
	/**
	 * Checks if a command is valid or not.
	 * @param c - the command that should be checked.
	 * @return true if the command is valid, otherwise false.
	 */
	public boolean validateCommand(Command c){
		
		// If game is connected to the server, only accept commands from current player. Unless you're battling.
		if(Constants.connectionState == ConnectionType.CONNECTED){
			if(GUICore.getInstance().currentView != ViewState.BATTLE)
				if(core.getPlayers().get(c.getSourcePlayerId()) != core.getCurrentPlayer())
					return false;
		}
		
		switch(c.commandType){
		case move:
			return validateMoveCommand(c);
		case attack:
			return validateAttackCommand(c);
		case activateAbility:
			return validateActivateAbilityCommand(c);
		case discardCard:
			return validateDiscardCardCommand(c);
		case playCard:
			return validatePlayCardCommand(c);
		case endTurn:
			return validateEndTurnCommand(c);
		case claimTile:
			return validateClaimTileCommand(c);
		case battleTarget:
			return validateBattleTargetCommand(c);
		case battleStart:
			return validateBattleStartCommand(c);
		}
		return false;
		
	}
	
	/**
	 * Method for checking if movement, attack... are in Range
	 * @param c - the command which being validated
	 * @return true if the Range are 1 
	 * otherwise returns false
	 */
	private boolean inRange(Command c){
		Tile t1 = MapUtil.getTileOfCard(core.getCard(c.getActingCardId()));
		Tile t2 = MapUtil.getTile(c.getTargetTileId());
		// Range are for now always 1
		if (MapUtil.getRange(t1, t2) == 1) 
			return true;
		return false;
	}
	
	
	private boolean validateMoveCommand(Command c){
		Tile pickedTile = MapUtil.getTile(c.getTargetTileId());
		Player owner = pickedTile.getOccupyingPlayer();
		Player currentPlayer = Core.getInstance().getCurrentPlayer();
		CreatureCard card;
		
		if(core.getCard(c.getActingCardId()) instanceof CreatureCard)
			card=(CreatureCard)core.getCard(c.getActingCardId());
		else
			return false;
		
		if(!card.hasActionLeft())
			return false;
		
		if(!pickedTile.hasRoomFor(card))
			return false;
				
		if(MapUtil.getTileOfCard(core.getCard(c.getActingCardId())) != null){
			if (pickedTile.getFirstCreature()== null || owner == currentPlayer){
				if (inRange(c)){
					return true;
				}
			}
		}	
		return false;
	}

	private boolean validateAttackCommand(Command c) {
		Tile pickedTile = MapUtil.getTile(c.getTargetTileId());
		Player currentPlayer = Core.getInstance().getCurrentPlayer();
		
		Card card = Core.getInstance().getCard(c.getActingCardId());
		if(card instanceof CreatureCard){
			if(((CreatureCard)card).getActionsLeft() <= 0)
				return false;
		}
		else
			return false;
		
		if ( inRange(c) && pickedTile.getOccupyingPlayer() != null){
			if (pickedTile.getOccupyingPlayer() != currentPlayer){
				return true;
			}
		}
		return false;
	}
	
	private boolean validateClaimTileCommand(Command c) {
		Card card = core.getCard(c.getActingCardId());
		if(card instanceof CreatureCard){
			CreatureCard actor = (CreatureCard)card;
			
			if(!actor.hasActionLeft())
				return false;
			if(MapUtil.getTileOfCard(actor) == null)
				return false;
			
			Tile targetTile = MapUtil.getTileOfCard(actor);
			if(targetTile.getOwningPlayer() != actor.getOwner()
					&& actor.hasActionLeft()){
					if(Rules.claimLandCost == Cost.always)
						if(core.getCurrentPlayer().getGold() >= 6)
							return true;
						else
							return false;
					return true;
			}
		}		
		return false;
	}
	
	private boolean validateActivateAbilityCommand(Command c) {
		Card card = core.getCard(c.actingCardId);
		Card targetCard = core.getCard(c.getTargetCardId()); // may be null if not targeted
		Effect e = card.getEffects().get(c.getEffectId());
		
		if(!e.canActivate())
			return false;
		
		if(e.isTargetType(TargetType.tile)){
			Tile t = MapUtil.getTile(c.getTargetTileId());
			if(t == null)
				return false;
			if(!e.canActivate(t))
				return false;
		}
		if(e.isTargetType(TargetType.buildingCard)){			
			if(!(targetCard instanceof BuildingCard) || targetCard == null)
				return false;
			if(!e.canActivate(targetCard))
				return false;
		}
		if(e.isTargetType(TargetType.creatureCard)){			
			if(!(targetCard instanceof CreatureCard) || targetCard == null)
				return false;
			if(!e.canActivate(targetCard))
				return false;
		}
		if(e.isTargetType(TargetType.player)){	
			Player targetPlayer = core.getPlayers().get(c.getTargetPlayerId());
			if(targetPlayer == null)
				return false;
			if(!e.canActivate(targetPlayer))
				return false;
		}
		
		return true;
	}
	private boolean validateDiscardCardCommand(Command c) {
		if (core.getCurrentPlayer().getHand() != null){
			return true;
		}
		return false;
	}
	private boolean validateEndTurnCommand(Command c){
		if (GUICore.getInstance().currentView == ViewState.MAP){
			return true;
		}
		return false;
	}
	private boolean validatePlayCardCommand(Command c) {
		Tile pickedTile = MapUtil.getTile(c.getTargetTileId());
		Player owner = pickedTile.getOccupyingPlayer();
		Player currentPlayer = Core.getInstance().getCurrentPlayer();
		
		Card playedCard = core.getCard(c.getActingCardId());
		
		if(playedCard instanceof CreatureCard){
			CreatureCard creature = (CreatureCard) playedCard;
			if(!pickedTile.hasRoomFor(creature))
				return false;
		}
		if (playedCard instanceof BuildingCard){
				if (!pickedTile.hasRoomFor((BuildingCard) playedCard)){
					return false;
				}
		}
		
		if(MapUtil.getTileOfCard(playedCard) == null){ // means card is not on gameboard, ie on hand.
			if(isAllowedArea(pickedTile, currentPlayer, Rules.playCreatureArea)){
				if (pickedTile.getFirstCreature() == null || owner == currentPlayer){
					if(Rules.playCardCost == Cost.always){
						if(core.getCard(c.getActingCardId()).getGoldCost() <= currentPlayer.getGold() &&
								core.getCard(c.getActingCardId()).getManaCost() <= currentPlayer.getMana())
							return true;
						else
							return false;
					}
					return true;
				}
			}
		}
		return false;
	}
	
	private boolean validateBattleTargetCommand(Command c){
		Battle battle = Battle.getInstance();
		CreatureCard attacker;
		CreatureCard defender;
		Player executorOfCommand = core.getPlayers().get(c.getSourcePlayerId());
		int attackingSideId = battle.getBattleIdOfPlayer(executorOfCommand);
		if(attackingSideId == -1)
			return false;
		int defendingSideId = Battle.otherPlayerId(attackingSideId);
		
		Card a = core.getCard(c.getActingCardId());
		if(a instanceof CreatureCard)
			attacker = (CreatureCard)a;
		else
			return false;
		
		Card d = core.getCard(c.getTargetCardId());
		if(d instanceof CreatureCard)
			defender = (CreatureCard)d;
		else
			return false;
		
		if(!attacker.hasActionLeft())
			return false;
		
		if(GUICore.getInstance().currentView != ViewState.BATTLE)
			return false;		
		
		if(!battle.getCreatures(attackingSideId).contains(attacker))
			return false;
		if(!battle.getCreatures(defendingSideId).contains(defender))
			return false;
		
		return true;
	}
	
	private boolean validateBattleStartCommand(Command c){
		Battle battle = Battle.getInstance();
		Player executorOfCommand = core.getPlayers().get(c.getSourcePlayerId());
		int actingSideId = battle.getBattleIdOfPlayer(executorOfCommand);
		if(actingSideId == -1)
			return false;
		
		if(!battle.isReadyToStart(actingSideId))
			return false;
		
		return true;
	}
	
	
	/**
	 * Checks if you are allowed to do something on the specified tile, depending on allowed Area.
	 * @param tile
	 * @param actingPlayer
	 * @param area
	 * @return
	 */
	private boolean isAllowedArea(Tile tile, Player actingPlayer, Area area){
		switch(area){
		case anywhere:
			return true;
		case ownedTiles:
			return (tile.getOwningPlayer() == actingPlayer);
		case startTile:
			return (MapUtil.getTile(actingPlayer.getStartPos()) == tile);
		default:
			return false;
		}
	}

}